﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        #region Mu律转A律

        /// <summary>
        /// Mu律数据文件转换到A律数据文件
        /// </summary>
        /// <param name="muLawFilePath">带头的Mu律数据文件路径</param>
        /// <param name="aLawFilePath">A律数据文件存储路径</param>
        public static void MuLawToALaw(string muLawFilePath, string aLawFilePath)
        {
            if (string.IsNullOrEmpty(muLawFilePath))
            {
                throw new ArgumentNullException(nameof(muLawFilePath));
            }

            if (!File.Exists(muLawFilePath))
            {
                throw new FileNotFoundException("带头的Mu律数据文件不存在", muLawFilePath);
            }

            if (string.IsNullOrEmpty(aLawFilePath))
            {
                throw new ArgumentNullException(nameof(aLawFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(muLawFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.MuLaw);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitiveMuLawToALaw(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(aLawFilePath);
                var destFileStream = new FileStream(aLawFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Adpcm);
                destFileStream.Write(destDataPtr.GetSpan());

                FillALawAndMuLawHead(destFileStream, WaveEncoding.ALaw, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length);
            }
        }

        /// <summary>
        /// Mu律数据转换为A律数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <returns>A律数据</returns>
        public static byte[] MuLawToALaw(byte[] muLawData)
        {
            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            byte[] aLawData = new byte[muLawData.Length];
            fixed (byte* aLawDataPtr = muLawData)
            {
                fixed (byte* muLawDataPtr = muLawData)
                {
                    PrimitiveMuLawToALaw(muLawDataPtr, aLawDataPtr, muLawData.Length);
                }
            }
            return aLawData;
        }

        /// <summary>
        /// A律数据转换为Mu律数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="muLawDataIndex">Mu律数据起始索引</param>
        /// <param name="aLawData">A律数据</param>
        /// <param name="aLawDataIndex">A律数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void MuLawToALaw(byte[] muLawData, int muLawDataIndex, byte[] aLawData, int aLawDataIndex, int length)
        {
            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            if (aLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(aLawDataIndex), "A律数据起始索引不能小于0");
            }

            if (muLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(muLawDataIndex), "Mu律数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (aLawDataIndex + length > aLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"A律数据起始索引\"加\"转换数据长度\"大于了\"A律数据\"长度");
            }

            if (muLawDataIndex + length > muLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Mu律数据起始索引\"加\"转换数据长度\"大于了\"Mu律数据\"长度");
            }

            fixed (byte* aLawDataPtr = muLawData)
            {
                fixed (byte* muLawDataPtr = muLawData)
                {
                    PrimitiveMuLawToALaw(muLawDataPtr + muLawDataIndex, aLawDataPtr + aLawDataIndex, length);
                }
            }
        }

        /// <summary>
        /// Mu律数据转换为A律数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="aLawData">A律数据</param>
        public static void MuLawToALaw(Span<byte> muLawData, Span<byte> aLawData)
        {
#if NET4_0
            byte* muLawDataPtr = muLawData.DataPtr;
            byte* aLawDataPtr = aLawData.DataPtr;            
#else
            byte* muLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref muLawData[0]);
            byte* aLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref aLawData[0]);
#endif
            PrimitiveMuLawToALaw(muLawDataPtr, aLawDataPtr, Math.Min(muLawData.Length, aLawData.Length));
        }

        /// <summary>
        /// Mu律数据转换为A律数据
        /// </summary>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="aLawData">A律数据</param>
        public static void MuLawToALaw(SpanZ<byte> muLawData, SpanZ<byte> aLawData)
        {
            PrimitiveMuLawToALaw(muLawData.DataPtr, aLawData.DataPtr, Math.Min(muLawData.Length, aLawData.Length));
        }

        //private static byte[] _u2a = null;
        private static void PrimitiveMuLawToALaw(byte* muLawDataPtr, byte* aLawDataPtr, int length)
        {
            //注:映射表法有失真,估计是表的问题,找了几个映射表效果都不太好,最终换成了先将mu律转换成pcm,再从pcm转换为a律

            /*
            if (_u2a == null)
            {
                _u2a = new byte[]
                {
                     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
                    ,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f
                    ,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f
                    ,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f
                    ,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f
                    ,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f
                    ,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f
                    ,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f
                    ,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f
                    ,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f
                    ,0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf
                    ,0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf
                    ,0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf
                    ,0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf
                    ,0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef
                    ,0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
                };
            }

            byte uVal;
            for (int i = 0; i < length; i++)
            {
                uVal = muLawDataPtr[i];
                uVal &= 0xFF;
                aLawDataPtr[i] = (byte)((uVal & 0x80) != 0 ? (0xD5 ^ (_u2a[0xFF ^ uVal] - 1)) : (0x55 ^ (_u2a[0x7F ^ uVal] - 1)));
            }

            */

            CheckALawSegEnd();
            int temp;
            byte uVal;
            short pcmVal;

            int mask;
            int seg;
            byte aVal;

            for (int i = 0; i < length; i++)
            {
                uVal = muLawDataPtr[i];

                //mu->pcm
                uVal = (byte)~uVal;
                temp = (uVal & QuantMask) << 3;
                temp += Bias;
                temp <<= ((uVal & SegMask) >> SegShift);
                pcmVal = (short)((uVal & SignBit) != 0 ? (Bias - temp) : (temp - Bias));


                //pcm->a
                if (pcmVal >= 0)
                {
                    mask = 0xD5;
                }
                else
                {
                    mask = 0x55;
                    pcmVal = (short)(-pcmVal - 1);
                }

                seg = Search(pcmVal, 8);
                if (seg >= 8)
                {
                    aLawDataPtr[i] = (byte)(0x7F ^ mask);
                }
                else
                {
                    aVal = (byte)(seg << SegShift);
                    if (seg < 2)
                    {
                        aVal |= (byte)((pcmVal >> 4) & QuantMask);
                    }
                    else
                    {
                        aVal |= (byte)((pcmVal >> (seg + 3)) & QuantMask);
                    }
                    aLawDataPtr[i] = (byte)(aVal ^ mask);
                }
            }

        }

        #endregion

        #region A律转Mu律

        /// <summary>
        /// A律数据文件转换到Mu律数据文件
        /// </summary>
        /// <param name="aLawFilePath">带头的A律数据文件路径</param>
        /// <param name="muLawFilePath">Mu律数据文件存储路径</param>
        public static void ALawToMuLaw(string aLawFilePath, string muLawFilePath)
        {
            if (string.IsNullOrEmpty(aLawFilePath))
            {
                throw new ArgumentNullException(nameof(aLawFilePath));
            }

            if (!File.Exists(aLawFilePath))
            {
                throw new FileNotFoundException("带头的A律数据文件不存在", aLawFilePath);
            }

            if (string.IsNullOrEmpty(muLawFilePath))
            {
                throw new ArgumentNullException(nameof(muLawFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(aLawFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.ALaw);
                var srcDataPtr = new UnmanagedMemoryPointer<byte>(srcFileStream.Length - srcFileWaveHeadSize);
                disposabler.Add(srcDataPtr, 0);

                var waveInfo = GetWavHead(srcFileStream);
                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.GetSpan());

                var destDataPtr = new UnmanagedMemoryPointer<byte>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitiveALawToMuLaw(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(muLawFilePath);
                var destFileStream = new FileStream(muLawFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.MuLaw);
                destFileStream.Write(destDataPtr.GetSpan());

                FillALawAndMuLawHead(destFileStream, WaveEncoding.MuLaw, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destDataPtr.Length);
            }
        }

        /// <summary>
        /// A律数据转换为Mu律数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <returns>Mu律数据</returns>
        public static byte[] ALawToMuLaw(byte[] aLawData)
        {
            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            byte[] muLawData = new byte[aLawData.Length];
            fixed (byte* aLawDataPtr = muLawData)
            {
                fixed (byte* muLawDataPtr = muLawData)
                {
                    PrimitiveALawToMuLaw(aLawDataPtr, muLawDataPtr, muLawData.Length);
                }
            }

            return muLawData;
        }

        /// <summary>
        /// A律数据转换为Mu律数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="aLawDataIndex">A律数据起始索引</param>
        /// <param name="muLawData">Mu律数据</param>
        /// <param name="muLawDataIndex">Mu律数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void ALawToMuLaw(byte[] aLawData, int aLawDataIndex, byte[] muLawData, int muLawDataIndex, int length)
        {
            if (aLawData == null)
            {
                throw new ArgumentNullException(nameof(aLawData));
            }

            if (muLawData == null)
            {
                throw new ArgumentNullException(nameof(muLawData));
            }

            if (aLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(aLawDataIndex), "A律数据起始索引不能小于0");
            }

            if (muLawDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(muLawDataIndex), "Mu律数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (aLawDataIndex + length > aLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"A律数据起始索引\"加\"转换数据长度\"大于了\"A律数据\"长度");
            }

            if (muLawDataIndex + length > muLawData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"Mu律数据起始索引\"加\"转换数据长度\"大于了\"Mu律数据\"长度");
            }

            fixed (byte* aLawDataPtr = muLawData)
            {
                fixed (byte* muLawDataPtr = muLawData)
                {
                    PrimitiveALawToMuLaw(aLawDataPtr + aLawDataIndex, muLawDataPtr + muLawDataIndex, length);
                }
            }
        }

        /// <summary>
        /// A律数据转换为Mu律数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="muLawData">Mu律数据</param>
        public static void ALawToMuLaw(Span<byte> aLawData, Span<byte> muLawData)
        {
#if NET4_0
            byte* muLawDataPtr = muLawData.DataPtr;
            byte* aLawDataPtr = aLawData.DataPtr;
#else
            byte* muLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref muLawData[0]);
            byte* aLawDataPtr = (byte*)Unsafe.AsPointer<byte>(ref aLawData[0]);
#endif

            PrimitiveALawToMuLaw(aLawDataPtr, muLawDataPtr, muLawData.Length);
        }

        /// <summary>
        /// A律数据转换为Mu律数据
        /// </summary>
        /// <param name="aLawData">A律数据</param>
        /// <param name="muLawData">Mu律数据</param>
        public static void ALawToMuLaw(SpanZ<byte> aLawData, SpanZ<byte> muLawData)
        {
            PrimitiveALawToMuLaw(aLawData.DataPtr, muLawData.DataPtr, Math.Min(muLawData.Length, aLawData.Length));
        }

        private static byte[] _a2u = null;
        private static void PrimitiveALawToMuLaw(byte* aLawDataPtr, byte* muLawDataPtr, int length)
        {
            if (_a2u == null)
            {
                _a2u = new byte[]
                {
                     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
                    ,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f
                    ,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f
                    ,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f
                    ,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f
                    ,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f
                    ,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f
                    ,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f
                    ,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f
                    ,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f
                    ,0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf
                    ,0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf
                    ,0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf
                    ,0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf
                    ,0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef
                    ,0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
                };
            }

            byte aVal;
            for (int i = 0; i < length; i++)
            {
                aVal = aLawDataPtr[i];
                aVal &= 0xFF;
                muLawDataPtr[i] = (byte)((aVal & 0x80) != 0 ? (0xFF ^ _a2u[aVal ^ 0xD5]) : (0x7F ^ _a2u[aVal ^ 0x55]));
            }
        }

        #endregion
    }
}
